﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ERF;
using ERF.Menu;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace FirstPerson.States
{
    class OptionsState : State
    {
        /// <summary>
        /// Singleton instance
        /// </summary>
        private static OptionsState instance;

        /// <summary>
        /// Get an instance of this state.
        /// </summary>
        /// <param name="machine">the state machine that this state should belong to.</param>
        /// <returns></returns>
        public static OptionsState GetInstance(StateMachine machine)
        {
            if (instance == null)
                instance = new OptionsState(machine);

            return instance;
        }

        private MenuList fullscreenMenu, resolutionsMenu, antialiasMenu, verticalSyncMenu;
        private MenuManager menuManager;
        private ResolutionInfo currentResolution;
        private MultiSampleType currentMultiSampleType;

        /// <summary>
        /// Constructor of OptionsState
        /// </summary>
        /// /// <summary>
        /// <param name="machine">the state machine that this state should belong to.</param>
        /// <returns></returns>
        private OptionsState(StateMachine machine)
            : base(machine)
        {
            this.menuManager = MenuManager.GetInstance(this.machine.CurrentGame);
        }

        public override void Initialize()
        {
            Menu menu = new Menu("Options");

            menu.MenuItems.Add(new MenuItem("Back", this.Back));

            // For now, there will be no options when playing on the 360.
#if !XBOX
            // Fullscreen option.
            this.SetupFullScreenOptions(menu);

            // Resolution option
            this.SetupResolutionOptions(menu);

            //Anti-alias option
            this.SetupAntialiasOptions(menu);

            //Vertical sync option.
            this.SetupVerticalSyncOptions(menu);

            menu.MenuItems.Add(new MenuItem("Apply changes", ApplyChanges));
#endif

            menu.SelectedIndex = 0;

            menuManager.Menu = menu;
        }

        private void SetupFullScreenOptions(Menu menu) 
        {
            this.fullscreenMenu = new MenuList("Fullscreen", menu, menu);
            this.fullscreenMenu.MenuItems.Add(new MenuValueItem("On", true, menu));
            this.fullscreenMenu.MenuItems.Add(new MenuValueItem("Off", false, menu));

            // Set the currently selected item to be On if the game is in fullscreen mode, and Off if it is not.
            this.fullscreenMenu.SelectedItem = base.machine.CurrentGame.IsFullscreen ? this.fullscreenMenu.MenuItems[0] : this.fullscreenMenu.MenuItems[1];
            menu.MenuItems.Add(this.fullscreenMenu);
        }

        private void SetupResolutionOptions(Menu menu)
        {
            this.resolutionsMenu = new MenuList("Resolution", menu, menu);
            int currentResolutionIndex = 0;

            List<ResolutionInfo> supportedResolutions = this.GetSupportedResolutions(out currentResolutionIndex);
            foreach (ResolutionInfo resolution in supportedResolutions)
            {
                this.resolutionsMenu.MenuItems.Add(new MenuValueItem(string.Format("{0}x{1}", resolution.width, resolution.height), resolution, menu));
            }

            // Set the currently selected item to be the resolution currently in use.
            this.resolutionsMenu.SelectedItem = this.resolutionsMenu.MenuItems[currentResolutionIndex];

            // And save a copy of the current resolution aswell, so we can compare against it when changes are applied.
            this.currentResolution = supportedResolutions[currentResolutionIndex];
            menu.MenuItems.Add(this.resolutionsMenu);
        }

        private void SetupAntialiasOptions(Menu menu)
        {
            this.antialiasMenu = new MenuList("Anti-alias", menu, menu);
            this.antialiasMenu.MenuItems.Add(new MenuValueItem("None", 0, menu));
            MultiSampleType[] supportedMultiSampleTypes = base.machine.CurrentGame.GetSupportedMultiSampleTypes();
            int currentMultiSampleIndex = Array.IndexOf(supportedMultiSampleTypes, base.machine.CurrentGame.CurrentMultiSampleType) + 1;
            for (int i = 0; i < supportedMultiSampleTypes.Length; i++)
                this.antialiasMenu.MenuItems.Add(new MenuValueItem(string.Format("{0}X", (int)supportedMultiSampleTypes[i]), (int)supportedMultiSampleTypes[i], menu));

            // Set the currently selected item to be the antialias setting currently in use.
            this.antialiasMenu.SelectedItem = antialiasMenu.MenuItems[currentMultiSampleIndex];

            // And save a copy of the current antialias setting aswell, so we can compare against it when changes are applied.
            this.currentMultiSampleType = (MultiSampleType)((MenuValueItem)antialiasMenu.MenuItems[currentMultiSampleIndex]).Value;
            menu.MenuItems.Add(this.antialiasMenu);
        }

        private void SetupVerticalSyncOptions(Menu menu)
        {
            this.verticalSyncMenu = new MenuList("Vertical sync", menu, menu);
            this.verticalSyncMenu.MenuItems.Add(new MenuValueItem("On", true, menu));
            this.verticalSyncMenu.MenuItems.Add(new MenuValueItem("Off", false, menu));

            // Set the currently selected item to be the vsync setting currently in use.
            this.verticalSyncMenu.SelectedItem = base.machine.CurrentGame.VerticalSync ? this.verticalSyncMenu.MenuItems[0] : this.verticalSyncMenu.MenuItems[1];

            menu.MenuItems.Add(verticalSyncMenu);
        }


        /// <summary>
        /// Applies any changes made by the user.
        /// </summary>
        private void ApplyChanges()
        {
            ERFGame game = base.machine.CurrentGame;

            // Apply fullscreen selection, if any.
            bool useFullscreen = (bool)((MenuValueItem)this.fullscreenMenu.SelectedItem).Value;
            if (game.IsFullscreen ^ useFullscreen)
                game.IsFullscreen = useFullscreen;

            // Apply resolution selection, if any.
            ResolutionInfo selectedResolution = (ResolutionInfo)((MenuValueItem)this.resolutionsMenu.SelectedItem).Value;
            if (!selectedResolution.Equals(this.currentResolution))
            {
                this.currentResolution = selectedResolution;
                game.SetResolution(selectedResolution.width, selectedResolution.height);
            }

            // Apply MultiSample selection, if any.
            MultiSampleType selectedMultiSampleType = (MultiSampleType)((MenuValueItem)this.antialiasMenu.SelectedItem).Value;
            if (!selectedMultiSampleType.Equals(this.currentMultiSampleType))
            {
                this.currentMultiSampleType = selectedMultiSampleType;
                game.CurrentMultiSampleType = selectedMultiSampleType;
            }

            // Apply vertical sync selection, if any.
            bool vsyncSelection = (bool)((MenuValueItem)this.verticalSyncMenu.SelectedItem).Value;
            if (vsyncSelection != game.VerticalSync)
            {
                game.VerticalSync = vsyncSelection;
            }
        }

        /// <summary>
        /// Returns a list of resolutions supported by the graphics device.
        /// </summary>
        /// <param name="currentResolutionIndex">Outputs the index of the current resolution in the returned list of resolutions.</param>
        /// <returns></returns>
        private List<ResolutionInfo> GetSupportedResolutions(out int currentResolutionIndex)
        {
            ERFGame game = base.machine.CurrentGame;

            List<ResolutionInfo> resolutions = new List<ResolutionInfo>();
            ResolutionInfo tempResolution;
            ResolutionInfo currentResolution = new ResolutionInfo(
                game.GraphicsDevice.PresentationParameters.BackBufferWidth,
                game.GraphicsDevice.PresentationParameters.BackBufferHeight);
            
            // Build a list of resolutions from the supported display modes.
            foreach (DisplayMode mode in game.GraphicsDevice.CreationParameters.Adapter.SupportedDisplayModes)
            {
                tempResolution = new ResolutionInfo(mode.Width, mode.Height);
                if (!resolutions.Contains(tempResolution)) 
                {
                    resolutions.Add(tempResolution);
                }
            }
            
            // Put the large resolutions on top.
            resolutions.Reverse();

            // Find the current resolution.
            currentResolutionIndex = 0;
            for(int i = 0; i < resolutions.Count; i++)
                if(resolutions[i].Equals(currentResolution))
                    currentResolutionIndex = i;


            return resolutions;
        }

        private void Back()
        {
            machine.CurrentState = machine.PreviousState;            
        }

        /// <summary>
        /// Begin PlayingState and hosting the game
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            if (input.IsButtonReleased("MENU_DOWN"))
            {
                menuManager.Menu.SelectNext();
            }

            if (input.IsButtonReleased("MENU_UP"))
            {
                menuManager.Menu.SelectPrevious();
            }

            if (input.IsButtonReleased("MENU_SELECT"))
            {
                menuManager.Menu.Submit();
            }

            if (input.IsButtonReleased("MENU_BACK"))
            {
                if (menuManager.Menu.ParentList != null)
                    menuManager.Menu.Back();
                else
                    Back();
            }
        }

        /// <summary>
        /// Represents a resolution.
        /// </summary>
        private struct ResolutionInfo
        {
            public int width;
            public int height;
            public ResolutionInfo(int width, int height)
            {
                this.width = width;
                this.height = height;
            }
        }        
    }
}
